/*
    Copyright 2010-2011 Patrik Jonsson, sunrise@familjenjonsson.org

    This file is part of Sunrise.

    Sunrise is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Sunrise is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Sunrise.  If not, see <http://www.gnu.org/licenses/>.

*/

/** \file 

    Declaration of the unstructured mesh grid classes used to
    interface with Arepo. NOTE that because this file pulls in the
    Arepo declarations, which contain many DEFINE's, this file should
    be included AFTER other files, because otherwise the risk of macro
    clashes is large. */

#ifndef __arepo_grid__
#define __arepo_grid__

namespace mcrx {
  template <typename> class arepo_grid;
  template <typename> class arepo_grid_cell;
};

#include "config.h"

#ifdef WITH_AREPO

#include "mcrx-types.h"
#include "ray.h"
#include <vector>
#include "mcrx-debug.h"
#include <cmath>
#include "blitz/numinquire.h"
#include "mcrx-arepo.h"
#include "mcrx-debug.h"
#include "mono_poly_abstract.h"

#ifdef HAVE_BOOST_SERIALIZATION
#include <boost/serialization/serialization.hpp>
#endif


// KLUDGE: we don't need these two here but they are included by arepo
// and they won't compile in C-linkage mode
#include "mpi.h"
#include "gmp.h"
extern "C" {
#include "proto.h"
#include "voronoi.h"
}

namespace arepo {
  extern tessellation* T;
};

/** Class representing an unstructured mesh grid generated by
    Arepo. This is only an interface class, the grid is actually
    handled by Arepo itself by using its datastructures. Note that
    this means there can not be more than one arepo_grid object, as
    the Arepo data structures are global. */
template <typename cell_data_type>
class mcrx::arepo_grid {
public:
  typedef mcrx::T_float T_float;
  typedef arepo_grid T_grid;
  typedef cell_data_type T_data;
  typedef arepo_grid_cell<T_data> T_cell;
  typedef typename T_cell::cell_tracker T_cell_tracker;
  friend  class arepo_grid_cell<T_data>;
  friend class T_cell::cell_tracker;

  typedef arepo::location_request T_location_request;
  typedef arepo::location_response T_location_response;

protected:
  /// The cell objects are kept in this vector
  std::vector<T_cell> cells_;

public:
  arepo_grid();

  virtual ~arepo_grid() {};

  T_cell_tracker locate (const vec3d &p, int, bool=false);
  T_location_response location_request(const T_location_request&) const;
  void initialize_tracker(const T_cell_tracker& code, 
			  const vec3d&,
			  T_cell_tracker&c) const;

  std::pair<T_cell_tracker, T_float> 
  intersection_from_without (const ray_base& ray, int);

  /// Returns the number of cells in the grid.
  int n_cells () const {return N_gas;};
  /// Returns cell i (this is the same as the cell with SphP index i).
  T_cell& cell(int i) {
    assert(i>=0); assert(i<cells_.size()); return cells_[i];};
  /// Returns cell i (this is the same as the cell with DP index i).
  const T_cell& cell(int i) const  {
    assert(i>=0); assert(i<cells_.size()); return cells_[i];};
  /// Returns the index of the cell pointed to by the tracker
  int get_cell_number(const T_cell_tracker& c) const {
    assert(c); return c.cell()->index();};

  /// Returns true if the point is within the grid
  bool in_grid(const vec3d& p) const {
    return arepo::in_arepo_box(p); };

  const vec3d& getmin() const {return arepo::arepomin;};
  const vec3d& getmax() const {return arepo::arepomax;};

  /// Throws assert if the point is not within the grid, with some
  /// leeway for roundoff.
  void assert_in_grid(const vec3d& p) const {
    arepo::assert_in_arepo_box(p); };

  // This is a dummy because it doesn't make sense for the Arepo grid.
  std::vector<bool> get_structure () const {
    assert(0); return std::vector<bool>();};

  class iterator;
  class const_iterator;

  const_iterator begin () const { return const_iterator(cells_.begin()); };
  iterator begin () { return iterator(cells_.begin()); };
  const_iterator end () const { return const_iterator(cells_.end()); };
  iterator end () { return iterator(cells_.end()); };

  /// The Arepo domain is always decomposed if we run many MPI tasks.
  bool shared_domain() const { return false; };

  /** Returns a vector of bools with a true for each position that is
      on this task. This function guarantees that when called with the
      same list of positions on all tasks, exactly one task owns each
      position, even those outside of the grid. */
  std::vector<bool> position_ownership(std::vector<vec3d>& pos) const {
    return arepo::position_ownership(pos); };
};


/** Class representing a Voronoi cell in the Arepo mesh. The actual
    mesh info is stored by Arepo itself, so this just interfaces with
    that. In this context, a "cell" is a Voronoi polygon and uniquely
    defined by its mesh point. */
template <typename cell_data_type> class mcrx::arepo_grid_cell {
public:
  typedef mcrx::T_float T_float;
  typedef arepo_grid_cell<cell_data_type> T_cell;
  typedef cell_data_type T_data;
  typedef arepo_grid<T_data> T_grid;
  friend  class arepo_grid<T_data>;
  friend int main(int, char**);

protected:
  /// The grid this cell is a member of.
  T_grid* g_;

  /** The index into the Arepo SphP array of the (primary) cell that
      corresponds to this mesh cell. Negative corresponds to an
      invalid cell. */
  int idx_;

  /// Pointer to the cell data object.
  T_data* data_;

  arepo_grid_cell(T_grid& g, int p) : g_(&g), idx_(p), data_(0) {};
public:
  arepo_grid_cell() : g_(0), idx_(-1), data_(0) {};

  operator bool() const {return idx_>=0;};

  /// Returns the pointer to the cell data object.
  T_data* data() const {return data_;};

  /** Sets the celldata pointer, deleting the object previously
      pointed to first. */
  void set_data(T_data* d) {if (data_) delete data_; assert (d); data_ = d;};

  /** Sets the celldata pointer, without deleting the object
      previously pointed to. This is used when the current celldata
      was created using placement (this is not optimal). */
  void set_data_nodelete(T_data* d) {data_ = d;};

  const T_grid* host_grid () const {assert (g_); return g_;};
  T_grid* host_grid () {assert (g_); return g_;};

  /** Returns the index into the cells_ array for this cell (which is
      also the index into the SphP Arepo array.) */
  ptrdiff_t index() const {const T_cell* c=&g_->cells_[0]; return this-c;};

  /** Returns the position of the primary mesh point associated with
      this cell, converted to Sunrise coordinates. */
  vec3d position() const { return arepo::get_point(idx_); };

  class cell_tracker;
  friend class cell_tracker;
  typedef cell_tracker T_cell_tracker;

  /// Returns the volume of the cell in Sunrise units.
  T_float volume() const {
    using arepo::lcon;
    return SphP[idx_].Volume*lcon*lcon*lcon; };

  /// Returns the MPI task of the cell. This is always ThisTask.
  int task() const { return ThisTask; };
};

/** The arepo_grid iterator class is a thin wrapper around an iterator
    to the vector holding the cells. */
template <typename T> 
class mcrx::arepo_grid<T>::iterator : 
  public std::iterator<std::forward_iterator_tag, 
		       typename mcrx::arepo_grid_cell<T> >  {
  typedef arepo_grid_cell<T> T_cell;
  typedef typename std::vector<T_cell>::iterator T_iterator_impl;

private:
  T_iterator_impl i_;

public:
public:
  iterator() : i_() {};

  iterator(const T_iterator_impl i) : i_(i) {};

  bool operator==(const iterator& rhs) const {return i_==rhs.i_; };
  bool operator!=(const iterator& rhs) const {return i_!=rhs.i_; };

  T_cell& operator*() const { return *i_; };
  T_cell* operator->() const { return &(*i_); };
  operator T_cell*() const { return &(*i_); };
  iterator& operator++ () { // prefix
    ++i_; return *this; };
  iterator operator++ (int){ // postfix
    iterator i (*this); operator++ (); return i;};

  int task() const { return i_->task(); };
  vec3d getcenter() const { return i_->position(); };
  bool contains(const vec3d& p) const { return i_->contains(p); };
  void assert_contains(const vec3d& p) const { i_->assert_contains(p); };
  T_float volume() const { return i_->volume(); };
  typename T_cell::T_data* data() const { return i_->data(); };

  std::ostream& print(std::ostream& os) const {
    os << "Arepo Iterator " << i_;
    return os;
  }
};


/** The arepo_grid iterator class is a thin wrapper around an iterator
    to the vector holding the cells. */
template <typename T> 
class mcrx::arepo_grid<T>::const_iterator :
  public std::iterator<std::forward_iterator_tag, 
		       typename mcrx::arepo_grid_cell<T> >  {
  typedef arepo_grid_cell<T> T_cell;
  typedef typename arepo_grid<T>::iterator T_iterator_impl;

private:
  T_iterator_impl i_;

public:
  const_iterator() : i_() {};
  const_iterator(const T_iterator_impl i) : i_(i) {};

  bool operator==(const const_iterator& rhs) const {return i_==rhs.i_; };
  bool operator!=(const const_iterator& rhs) const {return i_!=rhs.i_; };

  const T_cell& operator*() const { return *i_; };
  const T_cell* operator->() const { return i_; };
  operator const T_cell*() const { return i_; };
  const_iterator& operator++ () { // prefix
    ++i_; return *this; };
  const_iterator operator++ (int){ // postfix
    const_iterator i (*this); operator++ (); return i;};

  int task() const { return i_.task(); };
  vec3d getcenter() const { return i_.position(); };
  bool contains(const vec3d& p) const { return i_.contains(p); };
  void assert_contains(const vec3d& p) const { i_.assert_contains(p); };
  T_float volume() const { return i_.volume(); };
  typename T_cell::T_data* data() const { return i_.data(); };

  std::ostream& print(std::ostream& os) const {
    os << "Const " << i_;
    return os;
  }
};

/*
template <typename T>
mcrx::arepo_grid<T>::const_iterator
mcrx::arepo_grid<T>::begin () const 
{ return const_iterator(cells_.begin()); };

template <typename T>
mcrx::arepo_grid<T>::iterator
mcrx::arepo_grid<T>::begin () 
{ return iterator(cells_.begin()); };

template <typename T>
mcrx::arepo_grid<T>::const_iterator
mcrx::arepo_grid<T>::end () const
{ return const_iterator(cells_.end()); };

template <typename T>
mcrx::arepo_grid<T>::iterator
mcrx::arepo_grid<T>::end ()
{ return iterator(cells_.end()); };
*/

/** This class abstracts the concept of a "current cell" for the
    radiation transfer code. This class knows how to step through a
    Voronoi cell. */
template<typename T_data>
class mcrx::arepo_grid_cell<T_data>::cell_tracker {
public:
  typedef arepo_grid_cell<T_data> T_cell;
  typedef typename T_cell::T_cell_tracker T_cell_tracker;
  typedef T_cell_tracker T_code;
  friend class arepo_grid<T_data>;
  typedef arepo_grid_cell<T_data>* T_ptr;

private:

  /// The index of the Voronoi cell we are within
  int idx_;
  /// The MPI task of the cell we are within.
  int task_;
  /// The index of the cell we came from.
  int previous_;

  /** Pointer to the Sunrise data object associated with the cell. */
  arepo_grid_cell<T_data>* cell_; 

  T_cell* cell() const { return cell_; };

#ifdef HAVE_BOOST_SERIALIZATION
  friend class boost::serialization::access;
  /// Serialization support.
  template<class T_arch>
  void save(T_arch& ar, const unsigned int version) const {
    // do not save the pointer, because that saves the entire object
    ar << idx_ << task_ << previous_;
  };
  template<class T_arch>
  void load(T_arch& ar, const unsigned int version) {
    ar >> idx_ >> task_ >> previous_;
    // set cell pointer to null. must be initialized by the grid
    cell_ = 0;
  };
  BOOST_SERIALIZATION_SPLIT_MEMBER()
#endif

public:

  cell_tracker() : 
    idx_(-1), task_(-1), previous_(-1), cell_(0) {};

  /** Creates a cell tracker pointing to the cell sphp. */
  cell_tracker(int sphp, T_ptr c) : 
    idx_(sphp), task_(ThisTask), previous_(-1), cell_(c) {};

  /** Creates a cell tracker pointing to the specified index and task,
      but does not initialize the cell pointer. Used if the cell is on
      another task. */
  cell_tracker(int sphp, int task) : 
    idx_(sphp), task_(task), previous_(-1), cell_(0) {};

  // implicit copy constructor is fine
  /*
  cell_tracker(const cell_tracker& rhs) :
  idx_(rhs.idx_), task_(rhs.task_), previous_(rhs.previous_), 
  cell_(rhs.cell_) {};
  */

  ~cell_tracker() {};
  
  // implicit assignment operator is fine.
  /*
  cell_tracker& operator=(const cell_tracker& rhs) {
    dp_idx_ = rhs.dp_idx_;
    previous_cell_ = rhs.previous_cell_;
    cell_ = rhs.cell_;
    return *this;
  }
  */

  /** Equality comparison uses both cell and task. */
  bool operator==(const cell_tracker& rhs) const {
    return (idx_ == rhs.idx_) && (task_ == rhs.task_); };

  /** The tracker converts to true if it is pointing to a valid cell
      in this task, otherwise false. */
  operator bool() const {return (idx_ >= 0) && (task_==ThisTask);};

  /// Resets the tracker to be invalid.
  void reset() {idx_ = -1; task_ = -1; cell_ = 0; previous_ = -1; };

  /// Returns the MPI task of the cell pointed to by the tracker.
  int task() const { return task_; };

  /// Returns the index of the cell pointed to by the tracker.
  int index() const { return idx_; };

  /** Resets the cached intersection calculation. This MUST be called
      if the intersection routines are called repeatedly and the ray
      has changed direction or has been moved transverse to its
      direction. */
  void reset_intersections() { previous_=-1; };

  T_float 
  intersection_from_within (const ray_base& r, T_float max_len, 
			    T_densities& dn, bool& hit_max);

  /** Converts a fractional column density (optical depth) through the
      cell to a fractional physical distance through the cell. This is
      used when determining where in the cell a scattering takes
      place. */
  T_float column_to_distance_fraction(T_float fn, const vec3d& pos, 
				      const vec3d& dir, T_float L) const {
    return arepo::column_to_distance_fraction(idx_, 
					      fn, pos, dir, L); };

  typename T_cell::T_data* data() const {
    return cell_->data(); };

  T_float volume() const {
      return cell_->volume(); };

  /// Checks whether a position is within the cell. 
  bool contains(const vec3d& p) const;

  /// Asserts that a position is within the cell. Used for testing.
  void assert_contains(const vec3d& p) const {
    arepo::assert_contains(idx_, p); };

  std::ostream& print(std::ostream& os) const {
    os << index() << ", " << task();
    return os;
  }

  /** This is used to put the cell_tracker on a queue_item. While it's
      a no-op for this class, it's not for the octree. */
  const cell_tracker& code() const { return *this; };
};


/** Constructor creates cell vector. Before this is called, Arepo must
    have been called to load the snapshot. We don't want to load the
    snapshot here because we might want more than one grid referring
    to the Arepo data. */
template <typename cell_data_type>
mcrx::arepo_grid<cell_data_type>::arepo_grid ()
{
  // The cells refer to the N_gas primary cells.
  cells_.resize(N_gas);
  for(int i=0; i < N_gas; ++i) {
    cells_[i] = T_cell(*this, i);
  }
}


/** Returns the arepo_grid_cell object that contains the specified
    point. It uses the arepo::locate function. */
template <typename cell_data_type>
typename mcrx::arepo_grid<cell_data_type>::T_cell_tracker
mcrx::arepo_grid<cell_data_type>::locate (const vec3d & p, int thread, 
					  bool accept_outside)
{
  // most of the work is done by this function which has no knowledge
  // of anything outside of Arepo.
  const std::pair<int,int> loc = arepo::locate(p, thread, accept_outside);
  if (loc.first==-1)
    return T_cell_tracker();
  else if(loc.second==ThisTask)
    // location is on this task. initialize the cell pointer
    return T_cell_tracker(loc.first, &cells_[loc.first]);
  else
    // location is on another task. return a tracker pointing to that task/cell
    return T_cell_tracker(loc.first, loc.second);
}

/** Processes an incoming location request. This just entails finding
    the nearest local point. The parameter is non-const just because  */
template <typename cell_data_type>
arepo::location_response
mcrx::arepo_grid<cell_data_type>::
location_request(const T_location_request& req) const
{
  arepo::location_response r;

  r.mindist=req.mindist;
  r.task=ThisTask;
  r.idx = find_nearest_meshpoint_local(const_cast<double*>(&req.pos[0]),
				       -1, &r.mindist, 1, 0);

  return r;
}


/** This function initializes the tracker c with the info shipped over
    from another task. Note that for the arepo_grid, a copy of the
    tracker itself is stored in the queue_item, but that's not the
    case for the octree. */
template <typename cell_data_type>
void
mcrx::arepo_grid<cell_data_type>::initialize_tracker(const T_cell_tracker& code, 
						     const vec3d&,
						     T_cell_tracker&c) const
{
  assert(code.task() == ThisTask);
  assert(code.index()>=0 && code.index()<N_gas);
  c=code;
  c.cell_ = const_cast<T_cell*>(&cell(c.idx_));
  c.reset_intersections();
}


/** Checks if the ray hits the Arepo box and finds the intersection
 point.  Returns a cell_tracker object representing the cell the
 ray will enter if it hits the box as well as the distance between the
 ray position and the point where the ray enters the box.  If the ray
 misses, NaN and an invalid tracker_info is returned. If the ray is
 already inside the box, the cell containing the ray is returned. */
template <typename cell_data_type>
std::pair<typename mcrx::arepo_grid<cell_data_type>::T_cell_tracker, mcrx::T_float>
mcrx::arepo_grid<cell_data_type>::intersection_from_without (const ray_base& ray,
							     int thread)
{
  using arepo::lcon;

  // First test whether the ray already is inside the Arepo box.
  T_float l=0;
  ray_base temp_ray(ray);

  if(!arepo::in_arepo_box(ray.position())) {
    // It is not. Test if it will hit the box.
    l = ray.intersect_cube(arepo::arepomin, arepo::arepomax, false);
    if(l!=l) {
      DEBUG(2,std::cout << "Intersection from without: missed Arepo box" << std::endl;);
      // we missed the box
      return std::make_pair(T_cell_tracker(), l);
    }
    // It will hit the box. Propagate the temporary ray to the point on the face.
    temp_ray.propagate(l);
  }

  // Find the cell the ray will enter / is in.
  T_cell_tracker t = locate(temp_ray.position(), thread, true);
  if(t.index()<0) {
    DEBUG(1,std::cerr << "Error: intersection_from_without got no valid cell even though ray is in box!" << std::endl;);
    return std::make_pair(t, blitz::quiet_NaN(T_float()));
  }
    
  DEBUG(2,std::cout << "Intersection from without: hit valid cell " << t.idx_ << " after " << l << std::endl;);
  return std::make_pair(t, l);
}



/** Returns the result of propagating the ray through the Voronoi cell
    and into the next, subject to a maximum propagation distance of
    max_len. The propagated length is returned and dn is set to the
    column densities in the propagated segment. If the ray hit the max
    length, hit_max is set to true. The ray must be INSIDE this cell
    for this to be called. */
template<typename T_data>
//template<typename T_ptr>
mcrx::T_float
mcrx::arepo_grid_cell<T_data>::cell_tracker::
intersection_from_within (const ray_base& r, T_float max_len, 
			  T_densities& dn, bool& hit_max)
{
  using namespace arepo;

  assert(cell_);
  assert(task_==ThisTask);
  assert(*this);

  arepo::assert_contains(idx_, r.position());

  DEBUG(3,std::cout << "Intersection from within at pos " << r.position() << ", cell " << idx_ << std::endl;);

  // To find the intersection with the next cell, we use
  // find_next_cell which deals with the Arepo data and returns the DP
  // index of the next cell and the distance to the face.
 
  T_float len;
  std::pair<int, int> next = arepo::find_next_cell(r, idx_, previous_, len);


  // Get distance from current position to the next intersection point
  // (or to the max length point if we would pass it)
  T_float dl = std::min(max_len - r.length(), len);
  assert(dl>-1e-10);
  hit_max = (dl == max_len - r.length());

  // because of the periodic boundary, we will *always* find a next
  // cell. For this reason, we must explicitly test whether the end
  // point has exited the box.
  bool exited = (next.first<0);
  if(!in_arepo_box(r.position() + dl*r.direction())) {
    // we exited the box. now we must truncate the segment length to
    // the box edge
    dl = r.intersect_cube(arepo::arepomin,
			  arepo::arepomax, 
			  true);
    exited=true;
  }

  // Calculate the column density. Because the density has a linear
  // gradient, this is equal to the density at the center of the
  // segment.
  
  // Note that we do the calculation of the gradient.ray in *AREPO
  // length units* because that requires the fewest operations

  if( (SphP[idx_].Density>0) && 
      any(cell_->data()->get_absorber().densities()>0)) {

    // segment midpoint (in Arepo units)
    vec3d pmid(r.position() + 0.5*dl*r.direction());
    pmid*=(1./lcon);

    // the cell center may be on the opposite side of the periodic
    // boundary compared to the ray, in which case we need to wrap it.
    vec3d cell_center(SphP[idx_].Center[0],
		      SphP[idx_].Center[1],
		      SphP[idx_].Center[2]);
    cell_center = periodic_wrap_point(cell_center, pmid);

    const vec3d arepo_gradient(SphP[idx_].Grad.drho[0],
			       SphP[idx_].Grad.drho[1],
			       SphP[idx_].Grad.drho[2]);


    const vec3d delta_center(pmid - cell_center);

    // This expression calculates the ratio between the density at the
    // segment midpoint to the mean density of the cell. Take care to
    // get all lengths in arepo units.
    T_float rho_multi 
      (1 + 
       dot(arepo_gradient, delta_center)/
       SphP[idx_].Density);

#ifndef NDEBUG
    if(rho_multi<0) {
      // should never happen
      const int nearest1 = locate_brute(r.position()+1e-5*r.direction());
      const int nearest2 = locate_brute(r.position()+(dl-1e-5)*r.direction());
      const T_float rho1 = cell_density(idx_, r.position()/arepo::lcon);
      const T_float rho2 = 
	cell_density(idx_, (r.position()+dl*r.direction())/arepo::lcon);
      const T_float rho3 = cell_density(idx_, pmid);

      std::cerr << "Error: Rho_multi=" << rho_multi << " in cell " << idx_ << " start point in " << nearest1 << " endpoint in " << nearest2 << std::endl;
      std::cerr << "Enterpoint rho=" << rho1 << " exitpoint rho=" << rho2 << " midpoint rho=" << rho3 << std::endl;
      std::cerr << r.position() << vec3d(r.position()+dl*r.direction()) << std::endl;
      std::cerr << "previous cell is " << previous_ << std::endl;
      assert(rho_multi>=0);
    }
#endif

    // calculate the column density along the ray segment (can do this
    // in operator form but if only one density this is faster)
    if(dn.size()==1)
      dn(0) = cell_->data()->get_absorber().densities()(0) * rho_multi * dl;
    else
      dn = cell_->data()->get_absorber().densities() * rho_multi * dl;
    
    DEBUG(3,std::cout << "Determined dl=" << dl << ", rho_multi=" << rho_multi << ", cell density=" << cell_->data()->get_absorber().densities()(0) << ", column density=" << dn(0) << std::endl;);
  }
  else {
    DEBUG(3,std::cout << "Determined path length " << dl << " through empty cell" << std::endl;);
    // no need to do all this stuff if the cell density is zero.
    dn = 0;
  }

  if(exited){
    // if we exit the box, we need to "hard-terminate" the tracker.
    reset();
    return dl;
    DEBUG(2, std::cout << "Exiting volume" << std::endl;);
  }

  if(hit_max) {
    // we hit max length, then we terminate tracker, but we still have
    // a valid index and task.
    cell_=0;
    return dl;
  }
  else {
    // Update this cell tracker for next cell
    previous_ = idx_;
    idx_ = next.first;
    task_ = next.second;
    
    // if next cell is on this task, update the cell pointer
    if(task_ == ThisTask)
      cell_ = &cell_->host_grid()->cell(idx_);
    else
      cell_ = 0;
  }

  DEBUG(3, if(idx_>=0) std::cout << "Entering cell/task " << idx_ << '/' << task_ << std::endl;);

  return dl;
}    

#endif
#endif
